﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;
using System.Xml.XPath;
using System.Reflection;
using System.Configuration;

using SBPweb.Modules.Logging;
using SBPweb.Modules.Dependencies;
using System.Globalization;

namespace SBPweb.Modules.Modularity
{ 
    /// <summary>
    /// ModulesConfiguration
    /// </summary>
	public class ModulesConfiguration
	{

		#region Private fields

		private List<FileInfo> pModuleDlls;
		private string pModuleDllsPath;
        private string pPatternOfDlls;
        private ILogManager pLogManager;

		private Dictionary<string, ModuleDescriptor> pModuleDescriptors;
		private ModuleGroup pCoreModules;
		private Dictionary<string, ModuleGroup> pModuleGroups;
		private List<CultureInfo> pSupportedCultures;

        #endregion

        #region Constructor logic

        /// <summary>
        /// initializes a new ModulesConfiguration instance
        /// </summary>
        /// <param name="path">Path of the module dll files</param>
		/// <param name="pattern">Module filname pattern</param>
        /// <param name="logManager">An ILogManager instance</param>
        public ModulesConfiguration(string path, string pattern, ILogManager logManager)
        {
			pLogManager = logManager;
			pModuleDescriptors = new Dictionary<string, ModuleDescriptor>();
			pModuleGroups = new Dictionary<string, ModuleGroup>();
			pCoreModules = new ModuleGroup("Core");
			pModuleDllsPath = path;
            pPatternOfDlls = pattern;

			Initialize();
        }

        #endregion

		#region Public properties

		/// <summary>
		/// Dictionary of ModuleDescriptors, key is the ModuleTypeName of the module
		/// </summary>
		public Dictionary<string, ModuleDescriptor> ModuleDescriptors
		{
			get
			{
				return pModuleDescriptors;
			}
		}

		/// <summary>
		/// The ModuleGroup of the Core Modules
		/// </summary>
		public ModuleGroup CoreModules
		{
			get
			{
				return pCoreModules;
			}
		}

		/// <summary>
		/// Dictionary of ModuleGroups, key is the name of the ModuleGroup
		/// </summary>
		public Dictionary<string, ModuleGroup> ModuleGroups
		{
			get
			{
				return pModuleGroups;
			}
		}

		/// <summary>
		/// List of the supported cultures of the module
		/// </summary>
		public List<CultureInfo> SupportedCultures
		{
			get
			{
				return pSupportedCultures;
			}
		}

        /// <summary>
        /// The path where the modules are
        /// </summary>
        public string ModulesPath
        {
            get
            {
                return pModuleDllsPath;
            }
        }

        /// <summary>
        /// List of FileInfos of the modules
        /// </summary>
        public List<FileInfo> ModuleFileInfos
        {
            get
            {
                return pModuleDlls;
            }
        }

		#endregion

		#region Public methods
		
		/// <summary>
		/// Initializes the ModuleConfiguration instance
		/// </summary>
		public void Initialize()
		{
			DirectoryInfo dir = new DirectoryInfo(pModuleDllsPath);
			pModuleDlls = new List<FileInfo>(dir.GetFiles(pPatternOfDlls));
			List<string> supportedCultureNames = new List<string>();

            #region Cycle through moduleDlls

			foreach (FileInfo file in pModuleDlls)
			{
				string fileName = file.FullName;

				Configuration conf = ConfigurationManager.OpenExeConfiguration(fileName);
				if (conf != null)
				{
					ModuleProfile moduleProfile = (ModuleProfile)conf.GetSection("moduleProfile");
					if (moduleProfile != null)
					{

						foreach (string key in moduleProfile.SupportedCultures.AllKeys)
						{
							if (!supportedCultureNames.Contains(key))
							{
								supportedCultureNames.Add(key);
							}
						}

						string typeName = moduleProfile.TypeName;
						//string version = moduleProfile.Version;

						FileInfo assemblyFile = new FileInfo(fileName);
						ModuleDescriptor md = new ModuleDescriptor(assemblyFile, moduleProfile);

						if (md.ModuleTypeName == typeName/* && md.FileVersion == version*/)
						{
							pModuleDescriptors.Add(typeName, md);
						}
						else
						{
							pLogManager.WriteError(this, string.Format("Module type name mismatch ({0}, {1})", md.ModuleTypeName, typeName), "ModuleLoader");
#if DEBUG
							throw new ArgumentException(string.Format("Module type name mismatch ({0}, {1})", md.ModuleTypeName, typeName));
#endif
						}
					}
				}
			}

			#endregion

			#region Build SupportedCultures list

			pSupportedCultures = new List<CultureInfo>();
			foreach (string culture in supportedCultureNames)
			{
				bool ok = true;

				foreach (string typeName in pModuleDescriptors.Keys)
				{
					ModuleDescriptor md = pModuleDescriptors[typeName];
					if (md.ModuleProfile.SupportedCultures[culture] == null && md.ModuleProfile.SupportedCultures["*"] == null)
					{
						ok = false;
						break;
					}
				}
				if (ok)
				{
					try
					{
						pSupportedCultures.Add(new CultureInfo(culture));
					}
					catch (Exception ex)
					{
						pLogManager.WriteError(this, String.Format("Invalid culture: {0} - {1}", culture, ex), "ModuleLoader");
					}
				}
			}

			#endregion

			#region Cycle through moduleProfiles

			List<string> failedModuleDescriptors = new List<string>();
			foreach (string mdKey in pModuleDescriptors.Keys)
			{
				ModuleDescriptor md = pModuleDescriptors[mdKey];
				bool dependencyFail = false;

				foreach (KeyValueConfigurationElement kvce in md.ModuleProfile.ModuleVersionDependencies)
				{
					if (!string.IsNullOrEmpty(kvce.Key) && pModuleDescriptors.ContainsKey(kvce.Key))
					{
						ModuleDescriptor desc = pModuleDescriptors[kvce.Key];
						ModuleVersionDependency mvd = new ModuleVersionDependency(kvce.Value, desc);
						if (!mvd.IsMet)
						{
							dependencyFail = true;
							pLogManager.WriteCritical(this, string.Format("ModuleVersionDependency failed - {0}", mvd.ToString()), "ModuleLoader");
						}
					}
				}
				foreach (KeyValueConfigurationElement kvce in md.ModuleProfile.AssemblyVersionDependencies)
				{
					if (!string.IsNullOrEmpty(kvce.Key))
					{
						Assembly asm = GetReferencedAssembly(kvce.Key);
						AssemblyVersionDependency avd = new AssemblyVersionDependency(kvce.Value, asm);
						if (!avd.IsMet)
						{
							dependencyFail = true;
							pLogManager.WriteCritical(this, string.Format("AssemblyVersionDependency failed - {0}", avd.ToString()), "ModuleLoader");
						}
					}
				}

				if (dependencyFail)
				{
					failedModuleDescriptors.Add(mdKey);
				}
			}

			foreach (string mdKey in failedModuleDescriptors)
			{
				pModuleDescriptors.Remove(mdKey);
			}

			#endregion

			#region Build module groups

			foreach (string mdKey in pModuleDescriptors.Keys)
			{
				ModuleDescriptor md = pModuleDescriptors[mdKey];
				if (md.ModuleProfile.IsCoreModule)
				{
					CoreModules.Items.Add(new ModuleInfo(md, true, false, true));
				}
				else
				{
					foreach (KeyValueConfigurationElement kvce in md.ModuleProfile.ModuleGroups)
					{
						if (!string.IsNullOrEmpty(kvce.Key))
						{
							if (!pModuleGroups.ContainsKey(kvce.Key))
							{
								pModuleGroups.Add(kvce.Key, new ModuleGroup(kvce.Key));
							}
							pModuleGroups[kvce.Key].Items.Add(new ModuleInfo(md, kvce.Value == "Required", kvce.Value != "Required", false));
						}
					}
				}

			}

            #endregion

        }

        #endregion

        #region Public helper methods

		/// <summary>
		/// Returns a referenced asssembly by name
		/// </summary>
		/// <param name="asmName">string asmName</param>
		/// <returns>Assembly</returns>
		public static Assembly GetReferencedAssembly(string asmName)
        {
            AssemblyName[] asms = Assembly.GetEntryAssembly().GetReferencedAssemblies();
            foreach (AssemblyName an in asms)
            {
                if (an.Name == asmName)
                {
                    return Assembly.ReflectionOnlyLoad(an.FullName);
                }
            }
            return null;
        }

        #endregion

    }
}
